"""
This module provides the NonStrandSpecific class.
"""
import torch
from torch.nn.modules import Module

from .deeper_deepsea_arch import DeeperDeepSEA


def _flip(x, dim):
    """
    Reverses the elements in a given dimension `dim` of the Tensor.

    source: https://github.com/pytorch/pytorch/issues/229
    """
    xsize = x.size()
    dim = x.dim() + dim if dim < 0 else dim
    x = x.contiguous()
    x = x.view(-1, *xsize[dim:])
    x = x.view(
        x.size(0), x.size(1), -1)[:, getattr(
            torch.arange(x.size(1)-1, -1, -1),
            ('cpu','cuda')[x.is_cuda])().long(), :]
    return x.view(xsize)


class WrappedDeeperDeepSEA(Module):
    """
    A torch.nn.Module that wraps a user-specified model architecture if the
    architecture does not need to account for sequence strand-specificity.

    This is the NonStrandSpecific module from Selene. The only differences
    are that we renamed it to WrappedDeeperDeepSEA and we removed the
    `model` input parameter, directly importing `DeeperDeepSEA` and
    assigning `self.model` to it.

    Parameters
    ----------
    mode : {'mean', 'max'}, optional
        Default is 'mean'. WrappedDeeperDeepSEA will pass the input and the
        reverse-complement of the input into `model`. The mode specifies
        whether we should output the mean or max of the predictions as
        the non-strand specific prediction.

    Attributes
    ----------
    model : torch.nn.Module
        The user-specified model architecture.
    mode : {'mean', 'max'}
        How to handle outputting a non-strand specific prediction.

    """

    def __init__(self, mode="mean"):
        super(WrappedDeeperDeepSEA, self).__init__()

        self.model = DeeperDeepSEA(1000, 919)

        if mode != "mean" and mode != "max":
            raise ValueError("Mode should be one of 'mean' or 'max' but was"
                             "{0}.".format(mode))
        self.mode = mode

    def forward(self, input):
        reverse_input = _flip(
            _flip(input, 1), 2)

        output = self.model.forward(input)
        output_from_rev = self.model.forward(
            reverse_input)
        if self.mode == "mean":
            return (output + output_from_rev) / 2
        else:
            return torch.max(output, output_from_rev)

